#include "gap_gatts_internal.h"
#include <string.h>

extern struct configure configure;

extern uint8_t adv_config_done;
extern esp_ble_adv_params_t adv_params;

#ifdef CONFIG_SET_RAW_ADV_DATA
uint8_t raw_adv_data[] = {
	/* flags */
	0x02, 0x01, 0x06,
	/* tx power*/
	0x02, 0x0a, 0xeb,
	/* service uuid */
	0x03, 0x03, 0xFF, 0x00,
	/* device name */
	0x0f, 0x09, 'E', 'S', 'P', '_', 'G', 'A', 'T', 'T', 'S', '_', 'D', 'E',
	'M', 'O'
};
uint8_t raw_scan_rsp_data[] = {
	/* flags */
	0x02, 0x01, 0x06,
	/* tx power */
	0x02, 0x0a, 0xeb,
	/* service uuid */
	0x03, 0x03, 0xFF, 0x00
};

#else
uint8_t service_uuid[16] = {
	/* LSB
       <-------------------------------------------------------------------------------->
       MSB */
	// first uuid, 16bit, [12],[13] is the value
	0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
	0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};

/* The length of adv data must be less than 31 bytes */
esp_ble_adv_data_t adv_data = {
	.set_scan_rsp = false,
	.include_name = true,
	.include_txpower = true,
	.min_interval = 0x0006, // slave connection min interval, Time =
	// min_interval * 1.25 msec
	.max_interval = 0x0010, // slave connection max interval, Time =
	// max_interval * 1.25 msec
	.appearance = 0x00,
	.manufacturer_len = 0, // TEST_MANUFACTURER_DATA_LEN,
	.p_manufacturer_data = NULL, // test_manufacturer,
	.service_data_len = 0,
	.p_service_data = NULL,
	.service_uuid_len = sizeof(service_uuid),
	.p_service_uuid = service_uuid,
	.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// scan response data
esp_ble_adv_data_t scan_rsp_data = {
	.set_scan_rsp = true,
	.include_name = true,
	.include_txpower = true,
	.min_interval = 0x0006,
	.max_interval = 0x0010,
	.appearance = 0x00,
	.manufacturer_len = 0, // TEST_MANUFACTURER_DATA_LEN,
	.p_manufacturer_data = NULL, //&test_manufacturer[0],
	.service_data_len = 0,
	.p_service_data = NULL,
	.service_uuid_len = sizeof(service_uuid),
	.p_service_uuid = service_uuid,
	.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
#endif /* CONFIG_SET_RAW_ADV_DATA */

void reg_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		 esp_ble_gatts_cb_param_t *param)
{
	esp_err_t set_dev_name_ret =
		esp_ble_gap_set_device_name(configure.device_name);
	if (set_dev_name_ret) {
		ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x",
			 set_dev_name_ret);
		printf("device_name: %s\n", configure.device_name);
	}

#ifdef CONFIG_SET_RAW_ADV_DATA
	esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(
		raw_adv_data, sizeof(raw_adv_data));
	if (raw_adv_ret) {
		ESP_LOGE(GATTS_TAG,
			 "config raw adv data failed, error code = %x ",
			 raw_adv_ret);
	}
	adv_config_done |= ADV_CONFIG_FLAG;
	esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(
		raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
	if (raw_scan_ret) {
		ESP_LOGE(GATTS_TAG,
			 "config raw scan rsp data failed, error code = %x",
			 raw_scan_ret);
	}
	adv_config_done |= SCAN_RSP_CONFIG_FLAG;

#else
	// config adv data
	esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
	if (ret) {
		ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x",
			 ret);
	}
	adv_config_done |= ADV_CONFIG_FLAG;
	// config scan response data
	ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
	if (ret) {
		ESP_LOGE(GATTS_TAG,
			 "config scan response data failed, error code = %x",
			 ret);
	}
	adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#endif

	esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(
		configure.attr_db, gatts_if, configure.attr_num, SVC_INST_ID);
	if (create_attr_ret) {
		ESP_LOGE(GATTS_TAG, "create attr table failed, error code = %x",
			 create_attr_ret);
	}
}

// XXX
void read_cccd_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		       esp_ble_gatts_cb_param_t *param)
{
	esp_gatt_rsp_t rsp;
	memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
	rsp.attr_value.handle = param->read.handle;

	uint8_t *descr_value =
		configure.attr_db[get_idx_by_handle(param->write.handle)]
			.att_desc.value;

	rsp.attr_value.len = 2;
	rsp.attr_value.value[0] = descr_value[0];
	rsp.attr_value.value[1] = descr_value[1];
	esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
				    param->read.trans_id, ESP_GATT_OK, &rsp);
}

static void read_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
			 esp_ble_gatts_cb_param_t *param)
{
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_READ_EVT");

	struct gatts_uuid *uuid = NULL;
	uuid = get_char_uuid_by_cccd_handle(param->read.handle);
	if (uuid) {
		read_cccd_handler(event, gatts_if, param);
		return;
	}

	esp_gatt_rsp_t rsp;
	memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
	rsp.attr_value.handle = param->read.handle;

	uuid = get_char_uuid_by_char_handle(param->read.handle);
	if (uuid) {
		configure.profile->read_cb(uuid, rsp.attr_value.value,
					   &rsp.attr_value.len);
		esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
					    param->read.trans_id, ESP_GATT_OK,
					    &rsp);
		return;
	}
}

// XXX
void write_cccd_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
			esp_ble_gatts_cb_param_t *param)
{
	uint16_t descr_value = param->write.value[1] << 8 |
			       param->write.value[0];

	// set profile.cccd
	*(uint16_t *)configure.attr_db[get_idx_by_handle(param->write.handle)]
		 .att_desc.value = descr_value;

	// notify enable
	if (descr_value == 0x0001) {
		ESP_LOGI(GATTS_TAG, "notify enable");
	}

	// indicate enable
	if (descr_value == 0x0002) {
		ESP_LOGI(GATTS_TAG, "indicate enable");
	}

	// notify/indicate enable
	if (descr_value == 0x0003) {
		ESP_LOGI(GATTS_TAG, "notify/indicate enable");
	}

	// notify/indicate disable
	if (descr_value == 0x0000) {
		ESP_LOGI(GATTS_TAG, "notify/indicate disable");
	}

	/* send response when param->write.need_rsp is true*/
	if (param->write.need_rsp) {
		esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
					    param->write.trans_id, ESP_GATT_OK,
					    NULL);
	}

	// ESP_LOGE(GATTS_TAG, "unknown descr value");
	// esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
}

void write_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		   esp_ble_gatts_cb_param_t *param)
{
	if (param->write.is_prep) {
		/* handle prepare write */
		ESP_LOGW(GATTS_TAG, "recv too long: %u", param->write.len);
		return;
	}

	// if indicate/notify enable
	if (param->write.len == 2 && handle_is_cccd(param->write.handle)) {
		write_cccd_handler(event, gatts_if, param);
		return;
	}

	// the data length of gattc write  must be less than
	// GATTS_DEMO_CHAR_VAL_LEN_MAX.
	ESP_LOGI(GATTS_TAG,
		 "GATT_WRITE_EVT, handle = %d, value len = %d, value :",
		 param->write.handle, param->write.len);

	struct gatts_uuid *uuid =
		get_char_uuid_by_char_handle(param->write.handle);

	if (uuid) {
		configure.profile->write_cb(uuid, param->write.value,
					    param->write.len);
	}

	// char_value[0] = param->write.value[0];

	/* send response when param->write.need_rsp is true*/
	if (param->write.need_rsp) {
		esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
					    param->write.trans_id, ESP_GATT_OK,
					    NULL);
	}
}

void exec_write_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
			esp_ble_gatts_cb_param_t *param)
{
	// the length of gattc prepare write data must be less than
	// GATTS_DEMO_CHAR_VAL_LEN_MAX.
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
	// example_exec_write_event_env(&prepare_write_env, param);
}

void mtu_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		 esp_ble_gatts_cb_param_t *param)
{
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
}

void conf_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		  esp_ble_gatts_cb_param_t *param)
{
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d",
		 param->conf.status, param->conf.handle);
}

void start_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		   esp_ble_gatts_cb_param_t *param)
{
	ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d",
		 param->start.status, param->start.service_handle);
}

void connect_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
		     esp_ble_gatts_cb_param_t *param)
{
	configure.conn_id = param->connect.conn_id;
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d",
		 param->connect.conn_id);
	esp_log_buffer_hex(GATTS_TAG, param->connect.remote_bda, 6);
	esp_ble_conn_update_params_t conn_params = { 0 };
	memcpy(conn_params.bda, param->connect.remote_bda,
	       sizeof(esp_bd_addr_t));

	/* For the iOS system, please refer to Apple official documents about the
        * BLE connection parameters restrictions. 
        */
	conn_params.latency = 0;
	conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
	conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
	conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
	// start sent the update connection parameters to the peer device.
	esp_ble_gap_update_conn_params(&conn_params);
}

void disconnect_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
			esp_ble_gatts_cb_param_t *param)
{
	// exec_write_handler(event, gatts_if, param);
	ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x",
		 param->disconnect.reason);
	esp_ble_gap_start_advertising(&adv_params);
}

void create_attr_tab_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
			     esp_ble_gatts_cb_param_t *param)
{
	if (param->add_attr_tab.status != ESP_GATT_OK) {
		ESP_LOGE(GATTS_TAG,
			 "create attribute table failed, error code=0x%x",
			 param->add_attr_tab.status);
		return;
	}

	if (param->add_attr_tab.num_handle != configure.attr_num) {
		ESP_LOGE(GATTS_TAG,
			 "create attribute table abnormally, num_handle (%d) \
                doesn't equal to configure.attr_num(%d)",
			 param->add_attr_tab.num_handle, configure.attr_num);
		return;
	}

	ESP_LOGI(
		GATTS_TAG,
		"create attribute table successfully, the number handle = %d\n",
		param->add_attr_tab.num_handle);

	memcpy(configure.handle_list, param->add_attr_tab.handles,
	       sizeof(uint16_t) * configure.attr_num);

	for (int i = 0; i < configure.attr_num; i++) {
		printf("attr_idx: %u, attr_handle: %u\n", i,
		       param->add_attr_tab.handles[i]);
	}

	for (int i = 0; i < configure.service_num; i++) {
		esp_err_t r;
		printf("service_idx: %u, service_num: %u,config_handle: %u,attr_handle: %u\n",
		       configure.service_idx[i], configure.service_num,
		       configure.handle_list[configure.service_idx[i]],
		       param->add_attr_tab.handles[i]);
		r = esp_ble_gatts_start_service(
			configure.handle_list[configure.service_idx[i]]);

		if (r != ESP_OK) {
			ESP_LOGE(GATTS_TAG,
				 "start service error, service_num = %u",
				 configure.service_num);
			return;
		}
	}
}

struct handler_map {
	esp_gatts_cb_event_t event;
	esp_gatts_cb_t cb;
};

struct handler_map gatts_handler_map[] = {
	// clang-format off
        /*event                         callback*/
        {ESP_GATTS_REG_EVT,             reg_handler},
        {ESP_GATTS_READ_EVT,            read_handler},
        {ESP_GATTS_WRITE_EVT,           write_handler},
        {ESP_GATTS_EXEC_WRITE_EVT,      exec_write_handler},
        {ESP_GATTS_MTU_EVT,             mtu_handler},
        {ESP_GATTS_CONF_EVT,            conf_handler},
        {ESP_GATTS_START_EVT,           start_handler},
        {ESP_GATTS_CONNECT_EVT,         connect_handler},
        {ESP_GATTS_DISCONNECT_EVT,      disconnect_handler},
        {ESP_GATTS_CREAT_ATTR_TAB_EVT,  create_attr_tab_handler},
	// {ESP_GATTS_STOP_EVT,         },
	// {ESP_GATTS_OPEN_EVT,         },
	// {ESP_GATTS_CANCEL_OPEN_EVT,  },
	// {ESP_GATTS_CLOSE_EVT,        },
	// {ESP_GATTS_LISTEN_EVT,       },
	// {ESP_GATTS_CONGEST_EVT,      },
	// {ESP_GATTS_UNREG_EVT,        },
	// {ESP_GATTS_DELETE_EVT,       },
	// clang-format on
};

void gatts_profile_event_handler(esp_gatts_cb_event_t event,
				 esp_gatt_if_t gatts_if,
				 esp_ble_gatts_cb_param_t *param)
{
	for (int i = 0;
	     i < sizeof(gatts_handler_map) / sizeof(gatts_handler_map[0]);
	     i++) {
		if (gatts_handler_map[i].event == event) {
			gatts_handler_map[i].cb(event, gatts_if, param);
		}
	}
}
